﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FI.Core.Properties;
using FI.Core.Utilities;
using System.Collections;
using System.Configuration;
using FI.Core.SettingSource;

namespace FI.Core.Utilities
{
    /// <summary>
    /// This class is used to perform common argument validation.
    /// </summary>
    public static class Guard
    {
        /// <summary>
        /// Ensure the specified argument is not null.
        /// </summary>
        /// <param name="argumentValue">The argument value.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void ArgumentNotNull(object argumentValue, string argumentName)
        {
            if (argumentValue == null)
            {
                throw new ArgumentNullException(argumentName);
            }
        }

        /// <summary>
        /// Ensure the specified argument is not null or empty.
        /// </summary>
        /// <param name="argumentValue">The argument value.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void ArgumentNotNullOrEmpty(string argumentValue, string argumentName)
        {
            if (argumentValue == null)
            {
                throw new ArgumentNullException(argumentName);
            }
            if (argumentValue.Length == 0)
            {
                throw new ArgumentException(Resources.ExceptionArgumentMustNotBeEmpty, argumentName);
            }
        }

        /// <summary>
        /// Ensure the specified argument is not null or empty.
        /// </summary>
        /// <param name="argumentValue">The argument value.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void ArgumentNotEmpty(Guid argumentValue, string argumentName)
        {
            if (argumentValue == Guid.Empty)
            {
                throw new ArgumentException(Resources.ExceptionArgumentMustNotBeEmpty, argumentName);
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private static string GetTypeName(object assignmentInstance)
        {
            try
            {
                return assignmentInstance.GetType().FullName;
            }
            catch
            {
                return Resources.ExceptionUnknownType;
            }
        }

        /// <summary>
        /// Ensure the specified argument if of expected type.
        /// </summary>
        /// <param name="assignmentTargetType">Type of the assignment target.</param>
        /// <param name="assignmentInstance">The assignment instance.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void InstanceIsAssignable(Type assignmentTargetType, object assignmentInstance, string argumentName)
        {
            if (assignmentTargetType == null)
            {
                throw new ArgumentNullException("assignmentTargetType");
            }
            if (assignmentInstance == null)
            {
                throw new ArgumentNullException("assignmentInstance");
            }
            if (!assignmentTargetType.IsInstanceOfType(assignmentInstance))
            {
                throw new ArgumentException(Resources.ExceptionTypesAreNotAssignable.Format(assignmentTargetType, GetTypeName(assignmentInstance)), argumentName);
            }
        }

        /// <summary>
        /// Ensure the specified two type can be assignable.
        /// </summary>
        /// <param name="assignmentTargetType">Type of the assignment target.</param>
        /// <param name="assignmentValueType">Type of the assignment value.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void TypeIsAssignable(Type assignmentTargetType, Type assignmentValueType, string argumentName)
        {
            if (assignmentTargetType == null)
            {
                throw new ArgumentNullException("assignmentTargetType");
            }
            if (assignmentValueType == null)
            {
                throw new ArgumentNullException("assignmentValueType");
            }
            if (!assignmentTargetType.IsAssignableFrom(assignmentValueType))
            {
                throw new ArgumentException(Resources.ExceptionTypesAreNotAssignable.Format(assignmentTargetType, assignmentValueType), argumentName);
            }
        }
        private static readonly CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures);

        /// <summary>
        /// Ensure the give configurationsection exists, otherwize throw a <see cref="ConfigurationErrorsException"/> if miss the configuration section.
        /// </summary>
        /// <param name="configSectionName">Name of the configuration section.</param>
        public static void EnsureConfigSectionExists(string configSectionName)
        {
            Guard.ArgumentNotNullOrEmpty(configSectionName, "configSectionName");
            if (null == SettingSourceFactory.GetSettingSource().GetConfigurationSection(configSectionName))
            {
                throw new ConfigurationErrorsException(ResourceUtility.Format(Resources.ExceptionMissingConfigSection, configSectionName));
            }
        }

        /// <summary>
        /// Ensures the valid database name.
        /// </summary>
        /// <param name="databaseName">Name of the database.</param>
        public static void EnsureValidDatabaseName(string databaseName)
        {
            Guard.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            foreach (ConnectionStringSettings connectionSetting in ConfigManager.ConnectionStrings)
            {
                if (connectionSetting.Name == databaseName)
                {
                    return;
                }
            }
            throw new ConfigurationErrorsException(ResourceUtility.Format(Resources.ExceptionMissingConfigSection, databaseName));
        }

        /// <summary>
        /// Ensures the name of the valid culture.
        /// </summary>
        /// <param name="cultureName">Name of the culture.</param>
        public static void EnsureValidCultureName(string cultureName)
        {
            Guard.ArgumentNotNull(cultureName, "cultureName");
            if (!allCultures.Any(ci => ci.Name == cultureName))
            {
                throw new CultureNotFoundException(ResourceUtility.Format(Resources.ExceptionInvalidCultureName, cultureName));
            }
        }

        /// <summary>
        /// Ensures the sequence not null or empty.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sequence">The sequence.</param>
        /// <param name="sequenceArgumentName">Name of the sequence argurement.</param>
        public static void EnsureSequenceNotNullOrEmpty<T>(IEnumerable<T> sequence, string sequenceArgumentName)
        {
            Guard.ArgumentNotNull(sequence, "sequence");
            if (!sequence.Any())
            {
                throw new ArgumentException(ResourceUtility.Format(Resources.ExceptionSequenceNoElement), sequenceArgumentName);
            }
        }

        /// <summary>
        /// Determines whether [is assignable from] [the specified base type].
        /// </summary>
        /// <param name="baseType">Type of the base.</param>
        /// <param name="childType">Type of the child.</param>
        /// <returns><c>true</c> if [is assignable from] [the specified base type]; otherwise, <c>false</c>.</returns>
        public static bool IsAssignableFrom(Type baseType, Type childType)
        {
            Guard.ArgumentNotNull(baseType, "baseType");
            Guard.ArgumentNotNull(childType, "childType");

            while (!baseType.IsAssignableFrom(childType))
            {
                if (childType.Equals(typeof(object)))
                {
                    return false;
                }
                if (childType.IsGenericType && !childType.IsGenericTypeDefinition)
                {
                    childType = childType.GetGenericTypeDefinition();
                }
                else
                {
                    childType = childType.BaseType;
                }
            }
            return true;
        }

        /// <summary>
        /// Ensures the size of the valid page.
        /// </summary>
        /// <param name="pageSize">Size of the page.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void EnsureValidPageSize(int pageSize, string argumentName)
        {
            if (pageSize < 1)
            {
                throw new ArgumentException(Resources.ExceptionInvalidPageSize, argumentName);
            }
        }

        /// <summary>
        /// Ensures the index of the valid page.
        /// </summary>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void EnsureValidPageIndex(int pageIndex, string argumentName)
        {
            if (pageIndex < 1)
            {
                throw new ArgumentException(Resources.ExceptionInvalidPageIndex, argumentName);
            }
        }

        /// <summary>
        /// Arguments the not null or empty.
        /// </summary>
        /// <param name="list">The list.</param>
        /// <param name="argumentName">Name of the argument.</param>
        public static void ArgumentNotNullOrEmpty(IEnumerable list, string argumentName)
        {
            if (null == list)
            {
                throw new ArgumentNullException(argumentName);
            }
            if (!list.OfType<object>().Any())
            {
                throw new ArgumentException(Resources.ExceptionEmptyArrayOrList, argumentName);
            }
        }
    }
}
